﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Media;
using MSNPSharp;
using CGLib;
using CGLib.Enums;

namespace TienLen
{
    /// <summary>
    /// The game client form.
    /// </summary>
    public partial class Client : Form
    {
        #region Fields

        private List<Card> cardstack = new List<Card>(13); // Used for holding a list of 52 cards at the start & current cards on the field
        private List<PictureBox> cardpic = new List<PictureBox>(13); // Used as a temporary image cardholder, esp. for animation
        private List<PictureBox> disposequeue = new List<PictureBox>(); // Cards to dispose
        private CardSkins CardSkins = new CardSkins(); // Class which holds the cards
        private PictureBox[] slots; // Card field slots
        private List<PictureBox>[] picCards; // Picturebox cards

        private int playerfield = 0; // Used as a player number who has their cards on the field, and the amount of players ready in MSN hosting
        private bool allowFreeTurn = true; // If the cards should be cleared.

        private bool msgclose = true; // If true, a MessageBox will show warning the player of leaving the game

        private int playerturn = 1; // The current players turn
        private Player[] players = null; // The players
        private int xplace = 0; // Temporary place position for winner

        private Label[] labelnames; // Array of player name labels
        private SoundPlayer soundplay = new SoundPlayer();

        private Contact remoteOwner = null; // MSN conversation
        private Queue<MSNPacket> msnpQueue = null; // MSN packet queue for holding early packets

        private Size prevSize; // Previous client size (if resizing is enabled)
        private IGame game; // Current game

        private bool skipEnable = true;
        private bool clockwiseTurn = true;

        #endregion

        #region .ctor

        public Client(IGame gameAssembly, SvrType server, Player[] p, Contact remoteOwner)
        {
            game = gameAssembly;
            SvrFunc.ServerType = server;
            this.remoteOwner = remoteOwner;

            // Add the client game events
            GameEvents.ClientFuncCalled += new EventHandler<ClientFuncEventArgs>(GameEvents_ClientFuncCalled);
            GameEvents.ClientPropChangeCalled += new EventHandler<ClientPropChangeArgs>(GameEvents_ClientPropChangeCalled);

            InitializeComponent();
            this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
            Util.LoadCursor(this, CursorType.Default);

            labelnames = new Label[p.Length];
            slots = new PictureBox[] { slot_1, slot_2, slot_3, slot_4, slot_5, slot_6, slot_7, slot_8, slot_9, slot_10, slot_11, slot_12, slot_13 };
            picCards = new List<PictureBox>[p.Length];

            delay.Tag = GamePhase.None;

            // Create PictureBox card controls
            for (int i = 0; i < p.Length; i++)
            {
                picCards[i] = new List<PictureBox>();

                // Create a new label for the player's name
                Label newName = new Label();
                newName.AutoSize = true;
                newName.BackColor = System.Drawing.Color.Transparent;
                newName.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
                newName.ForeColor = System.Drawing.Color.White;

                this.Controls.Add(newName);
                labelnames[i] = newName;

                for (int j = 0; j < 13; j++)
                {
                    PictureBox newPic = new PictureBox();
                    newPic.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
                    newPic.Visible = false;
                    newPic.Size = new Size(168, 244);

                    this.Controls.Add(newPic);
                    picCards[i].Add(newPic);
                }
            }

            // Copy the players info
            players = p;

            if (SvrFunc.ServerType == SvrType.MSN)
            {
                SvrFunc.MSN.MessageManager.TextMessageReceived += new EventHandler<TextMessageArrivedEventArgs>(MessageManager_TextMessageReceived);
                SvrFunc.MSN.Nameserver.LeftGroupChat += new EventHandler<GroupChatParticipationEventArgs>(Nameserver_LeftGroupChat);

                msnpQueue = new Queue<MSNPacket>();

                btnSend.Dispose();
            }
            else
            {
                this.Size = new Size(this.Size.Width, this.Size.Height - ctable.Size.Height - 30);

                // If we are playing offline, remove chat controls and resize.
                ctable.Controls.Clear();
                ctable.Dispose();
            }
            
            #region Set the Settings

            if (TienLen.Properties.Settings.Default.opt_soundeffect == false)
                soundplay.Dispose();

            if (TienLen.Properties.Settings.Default.opt_showbg)
                this.BackgroundImage = Image.FromFile(CardSkins.SelectedSkinDirectory + "bg.jpg");
            else
            {
                infobox.ForeColor = Color.Black;
                for (int x = 0; x < players.Length; x++) labelnames[x].ForeColor = Color.Black;
            }

            if (TienLen.Properties.Settings.Default.opt_resize)
            {
                this.FormBorderStyle = FormBorderStyle.SizableToolWindow;
                this.ResizeEnd += new EventHandler(Client_ResizeEnd);

                prevSize = this.Size;
            }

            #endregion

            game.Initialised(new GameInfo(players, SvrFunc.ServerType));

            if (players[0].Type == UserType.Host)
                HostBegin();

            this.Size = new Size((int)(Screen.PrimaryScreen.WorkingArea.Width * 0.625f),
                (int)(Screen.PrimaryScreen.WorkingArea.Height * 0.7f) + (!ctable.IsDisposed ? ctable.Size.Height : 0));

            this.prevSize = this.Size;
            ChangeSize(this.Size);
        }

        private void GameEvents_ClientPropChangeCalled(object sender, ClientPropChangeArgs e)
        {
            switch (e.PropChangeType)
            {
                case ClientPropChange.PlayerTurn:
                    
                    playerturn = (int)e.Info;
                    break;

                case ClientPropChange.Skippable:

                    skipEnable = (bool)e.Info;

                    if (btnOK.Text == "Skip Turn")
                        btnOK.Enabled = (bool)e.Info;

                    break;

                case ClientPropChange.AllowFreeTurn:

                    allowFreeTurn = (bool)e.Info;
                    break;

                case ClientPropChange.ClockwiseTurn:

                    clockwiseTurn = (bool)e.Info;
                    break;
            }
        }

        private void GameEvents_ClientFuncCalled(object sender, ClientFuncEventArgs e)
        {
            switch (e.CallType)
            {
                case ClientCall.SubMsg:

                    infobox.Text = e.Text;
                    StartDelayTimer(1500, GamePhase.Message);

                    break;

                case ClientCall.MSNText:

                    remoteOwner.SendMessage(new TextMessage(e.Text));
                    break;

            }
        }

        #endregion

        #region MSN Functions & Events

        /// <summary>
        /// Get the player number from the players email or AI ID.
        /// </summary>
        private int GetPlayerNum(string mailID)
        {
            for (int x = 1; x <= players.Length; x++)
                if (players[x - 1].ID == mailID) return x;

            return -1;
        }

        /// <summary>
        /// [MSN] - Send a IFC message to the conversation.
        /// </summary>
        /// <remarks>
        /// @TLC IFC [MAIL] [XX,Y/]...
        /// This message is sent by the host, where he sends information about ones players cards.
        /// </remarks>
        private void SendIFCMessage(int player)
        {
            string msgsend = "@TLC IFC " + players[player - 1].ID + " ";

            for (int i = 0; i < cardpic.Count; i++)
            {
                Card card = (Card)cardpic[i].Tag;

                msgsend += ((int)card.Value).ToString() + "," + ((int)card.Suite).ToString() +
                    (i != cardpic.Count - 1 ? "/" : string.Empty);
            }

            remoteOwner.SendMessage(new TextMessage(msgsend));
        }


        private void Nameserver_LeftGroupChat(object sender, GroupChatParticipationEventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new EventHandler<GroupChatParticipationEventArgs>(Nameserver_LeftGroupChat), sender, e);
                return;
            }

            if (e.Via != remoteOwner)
                return;

            for (int x = 0; x < players.Length; x++)
            {
                if (players[x].ID == e.Contact.Account)
                {
                    infobox.Text = e.Contact.Name + " has left the game!";
                    StartDelayTimer(2200, GamePhase.Message);

                    players[x].Type = UserType.Leaver;

                    if (players[x].Results.Place != 0)
                        players[x].Results.Place = players.Length - xplace++;

                    if (remoteOwner.ContactList[IMAddressInfoType.None].Count != 0)
                    {
                        if (players[x].Type == UserType.Host)
                        {
                            exnote.Text = remoteOwner.Name + " is now the host.";

                            (Array.Find(players,
                                delegate(Player player) { return player.ID == remoteOwner.Mail; })).Type = UserType.Host;
                        }
                    }
                    else
                    {
                        EndGame();
                    }

                    xplace++;
                    return;
                }
            }
        }

        private void MessageManager_TextMessageReceived(object sender, TextMessageArrivedEventArgs e)
        {
            if (e.Sender != remoteOwner) return;

            if (InvokeRequired)
            {
                Invoke(new EventHandler<TextMessageArrivedEventArgs>(MessageManager_TextMessageReceived), sender, e);
                return;
            }

            bool success;
            MSNPacket packet = new MSNPacket((e as TextMessageArrivedEventArgs).TextMessage.Text, out success);

            if (success)
            {
                switch (packet.Type)
                {
                    case MsgType.Custom:

                        CoreClient.CallOnEvent(OnEventCallType.MSNPacketReceived, packet);
                        break;

                    case MsgType.CardInfo:

                        int playerNum = GetPlayerNum(packet.Info[0]);
                        string[] cinfo = packet.Info[1].Split('/');

                        cardpic = GetPicList(playerNum);

                        for (int i = 0; i < cinfo.Length; i++)
                        {
                            string[] split = cinfo[i].Split(',');
                            players[playerNum - 1].Cards.Add(new Card((Suite)Convert.ToInt32(split[1]), (Value)Convert.ToInt32(split[0])));
                        }

                        LabelPlayerInfo(players[playerNum - 1].Cards, playerNum);

                        for (int j = 0; j < players[playerNum - 1].Cards.Count; j++)
                            cardpic[j].Tag = new CardAnim(players[playerNum - 1].Cards[j]);

                        RenderCards(playerNum);

                        break;

                    case MsgType.Ready:

                        if (packet.Info[0] == "A")
                        {
                            if (players[0].Type == UserType.Host)
                            {
                                playerfield++;

                                if (playerfield == remoteOwner.ContactList[IMAddressInfoType.None].Count)
                                {
                                    playerfield = 0;

                                    remoteOwner.SendMessage(new TextMessage("@TLC RDY C"));
                                    ChangeTurn(true);
                                }
                            }
                            else
                                remoteOwner.SendMessage(new TextMessage("@TLC RDY A"));
                        }
                        else if (packet.Info[0] == "C")
                        {
                            BeginGame();
                            ChangeTurn(true);
                        }

                        break;


                    case MsgType.FieldCards:

                        if (packet.Info[0] == "A")
                        {
                            // We assume that the packet has come too early (eg. AI). Hold this and process it later.
                            if (GetPlayerNum(packet.Info[1]) != playerturn)
                            {
                                msnpQueue.Enqueue(packet);
                                return;
                            }
                            else
                                ParseFieldPacket(packet);
                        }
                        else if (packet.Info[0] == "B")
                            infobox.Text = players[playerturn - 1].Name + " has decided to skip the turn.";

                        EndTurn();
                        break;
                }

                CoreClient.CallOnEvent(OnEventCallType.MSNPacketReceived, packet);
            }
            else
            {
                AppendText(e.Sender.Name, (e as TextMessageArrivedEventArgs).TextMessage.Text);
            }
        }

        private void ParseFieldPacket(MSNPacket packet)
        {
            string[] split = packet.Info[2].Split('/');

            for (int x = 0; x < split.Length; x++)
                cardpic.Add(CardOrdToPic(Convert.ToInt32(split[x]), playerturn));
        }

        #endregion

        #region Card Methods

        /// <summary>
        /// Generates the list with PictureBox's belonging to the player.
        /// </summary>
        /// <param name="player">To player number to use to obtain the PictureBox's.</param>
        private List<PictureBox> GetPicList(int player)
        {
            return new List<PictureBox>(picCards[player - 1]);
        }

        /// <summary>
        /// Get the PictureBox with the card orientation info provided.
        /// </summary>
        /// <param name="cardorder">The card order from the players cards.</param>
        /// <param name="player">The player PictureBox you want to retrieve, depending on the card order.</param>
        private PictureBox CardOrdToPic(int cardorder, int player)
        {
            return picCards[player - 1][cardorder - 1];
        }

        /// <summary>
        /// Generates cards for the player.
        /// </summary>
        /// <param name="player">The player to have their cards generated.</param>
        private CardAnim[] GenerateCards(int player)
        {
            // Random number generator
            Random numram = new Random(DateTime.Now.Millisecond);
            int ramnumber = 0;
            CardAnim[] cards = new CardAnim[13];

            for (int x = 0; x < 13; x++)
            {
                ramnumber = numram.Next(0, cardstack.Count);

                cards[x] = (CardAnim)cardstack[ramnumber];
                cardstack.Remove(cardstack[ramnumber]);
            }

            return cards;
        }

        /// <summary>
        /// Reveal a card, changing the image to a playing card.
        /// </summary>
        /// <param name="pic">The PictureBox to reveal.</param>
        private void RevealCards(PictureBox pic)
        {
            pic.Image = Image.FromFile(CardSkins.GetCardPath((Card)pic.Tag));
        }

        /// <summary>
        /// Replace the PictureBox image with the back display of the playing card.
        /// </summary>
        /// <param name="pic"></param>
        private void HideCard(PictureBox pic)
        {
            pic.Image = Image.FromFile(CardSkins.SelectedSkinDirectory + "cover.png");
        }

        /// <summary>
        /// Set further necessary card information to the players PictureBox tags.
        /// </summary>
        /// <param name="player">The player to give extra information to.</param>
        void LabelPlayerInfo(List<Card> cards, int player)
        {
            for (int i = 0; i < cards.Count; i++)
            {
                cards[i].Player = player;
                cards[i].CardOrder = i + 1;
            }
        }

        #endregion

        #region Interface Related

        #region Chat Controls

        /// <summary>
        /// Add the text message into the chatbox.
        /// </summary>
        /// <param name="name">The name of the sender.</param>
        /// <param name="text">The text message.</param>
        private void AppendText(string name, string text)
        {
            if (SvrFunc.MSN.Owner.Name == name)
                cbox.SelectionColor = Color.Red;
            else cbox.SelectionColor = Color.Blue;

            cbox.SelectionFont = new Font(cbox.Font, FontStyle.Bold);
            cbox.AppendText(name + ": ");


            cbox.SelectionColor = Color.Black;
            cbox.SelectionFont = new Font(cbox.Font, FontStyle.Regular);
            cbox.AppendText(text + "\r\n");
        }

        private void cwrite_TextChanged(object sender, EventArgs e)
        {
            csend.Enabled = !string.IsNullOrEmpty(cwrite.Text);
        }

        private void csend_Click(object sender, EventArgs e)
        {
            remoteOwner.SendMessage(new TextMessage(cwrite.Text));
            AppendText(SvrFunc.MSN.Owner.Name, cwrite.Text);

            cwrite.Multiline = false;
            cwrite.Text = string.Empty;
            cwrite.Multiline = true;
        }

        #endregion

        #region Resising / Full Screen

        /// <summary>
        /// Get where the card should be located in the game, depending on the form size.
        /// </summary>
        /// <param name="picSize">The card image size.</param>
        /// <param name="player">The players card.</param>
        /// <param name="index">Index = card order - 1</param>
        private Point GetCardLocation(Size picSize, int player, int index)
        {
            if (player == 1 || player == 3)
                return new Point((int)((this.Size.Width / 2) - ((6.5d - index) * (this.Size.Width / 30))), player == 1 ? GetFieldSize().Height - picSize.Height - 15 : 15);
            else /* if (card.Player == 2 || card.Player == 4) */
                return new Point(player == 2 ? ((int)((float)this.Size.Width - ((float)picSize.Width * 1.25f))) : (int)((float)picSize.Width * 0.25f),
                    (int)((GetFieldSize().Height / 2.25f) - ((8d - index) * (this.Size.Height / 25))));
        }

        /// <summary>
        /// Automatically resize the menustrip.
        /// </summary>
        private void ResizeMenus()
        {
            int newWidth = menu.Size.Width / menu.Items.Count;

            menu.Size = new Size(menu.Size.Width, (int)(this.Size.Height * 0.05f));

            for (int i = 0; i < menu.Items.Count; i++)
            {
                menu.Items[i].Size = new Size(newWidth - 5, menu.Size.Height);
                menu.Items[i].Font = new Font(menu.Font.FontFamily, this.Size.Height * 0.017f, menu.Items[i].Font.Bold == true ? FontStyle.Bold : FontStyle.Regular);
            }
        }

        /// <summary>
        /// Get the size of the field, which excludes the menustrip and the (chat section if not disposed).
        /// </summary>
        private Size GetFieldSize()
        {
            return new Size(this.Size.Width, this.Size.Height - menu.Size.Height - (!ctable.IsDisposed ? ctable.Size.Height + 15 : 50));
        }

        private void ChangeSize(Size size)
        {
            List<PictureBox> pics = new List<PictureBox>(13);
            Point point;
            bool chatInvis = true;

            // If chat-table is visible, hide it for now so we don't get excessive flicker
            if (ctable.Visible)
            {
                ctable.Visible = false;
                this.Focus();

                chatInvis = false;
            }

            // Full Screen set!
            if (size == Screen.PrimaryScreen.Bounds.Size) this.Location = new Point(0, 0);
            this.FormBorderStyle = (size == Screen.PrimaryScreen.Bounds.Size) ? FormBorderStyle.None :
                (TienLen.Properties.Settings.Default.opt_resize ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow);
            this.Size = size;

            // Get the new card size for all undisposed cards
            // New width = old width / old height * new height (aspect ratio formula)
            Size newCardSize = new Size((int)((float)CardOrdToPic(1, 1).Width / (float)CardOrdToPic(1, 1).Height * (GetFieldSize().Height / 4.175f)),
                (int)(GetFieldSize().Height / 4.175f));

            ResizeMenus();

            // Set the slot positions
            for (int j = 0; j < 13; j++)
                slots[j].Location = new Point((int)(GetFieldSize().Width / 2 - ((6.5d - j) * (size.Width / 30))), (int)(GetFieldSize().Height / 2.6f));

            for (int i = 1; i <= players.Length; i++)
            {
                pics = GetPicList(i);

                for (int j = 0; j < 13; j++)
                {
                    PictureBox pic = pics[j];
                    CardAnim tag = (CardAnim)pic.Tag;

                    if (pic.IsDisposed) continue;

                    pic.Size = newCardSize;

                    if (tag.TimerAnim == null && disposequeue.Find(delegate(PictureBox picSearch) { return pic == picSearch; }) != null)
                        pic.Location = slots[((CardAnim)pic.Tag).CentreSlot - 1].Location;
                    else
                    {
                        pic.Location = GetCardLocation(pic.Size, i, j);

                        if (tag.TimerAnim != null)
                        {
                            tag.TimerAnim.Enabled = false;

                            if ((int)tag.TimerAnim.Tag == 0)
                                pic.Location = new Point(pic.Location.X, pic.Location.Y - (!tag.IsSelected ? 40 - ((int)tag.TimerAnim.Elasped * 2) : (int)tag.TimerAnim.Elasped * 2));
                            else if ((int)tag.TimerAnim.Tag == 1)
                            {
                                tag.TimerAnim.SizeIncrement = new Point((slots[tag.CentreSlot - 1].Location.X - pic.Location.X) / 10, (slots[tag.CentreSlot - 1].Location.Y - pic.Location.Y) / 10);
                                pic.Location = new Point(GetCardLocation(newCardSize, i, j).X + (tag.TimerAnim.SizeIncrement.X * tag.TimerAnim.Elasped), GetCardLocation(newCardSize, i, j).Y + (tag.TimerAnim.SizeIncrement.Y * tag.TimerAnim.Elasped));
                            }

                            tag.TimerAnim.Start();
                        }
                        else if (((CardAnim)pic.Tag).IsSelected)
                            pic.Location = new Point(pic.Location.X, pic.Location.Y - 20);
                    }
                }

                // Resize & relocate the player labels
                labelnames[i - 1].Font = new Font(labelnames[i - 1].Font.FontFamily, GetFieldSize().Height / 60, FontStyle.Bold);

                switch (i)
                {
                    case 1:
                        point = GetCardLocation(newCardSize, 1, 0);

                        labelnames[i - 1].Location = new Point(point.X - labelnames[i - 1].Size.Width - 5, point.Y + newCardSize.Height - 25);
                        break;
                    case 2:
                        point = GetCardLocation(newCardSize, 2, 12);

                        labelnames[i - 1].Location = new Point(point.X + newCardSize.Width - labelnames[i - 1].Size.Width, point.Y + newCardSize.Height + 5);
                        break;
                    case 3:
                        point = GetCardLocation(newCardSize, 3, 12);

                        labelnames[i - 1].Location = new Point(point.X + newCardSize.Width + 5, point.Y);
                        break;
                    case 4:
                        point = GetCardLocation(newCardSize, 4, 0);

                        labelnames[i - 1].Location = new Point(point.X, point.Y - 30);
                        break;
                }
            }

            // Set the infobox location, font & size
            point = GetCardLocation(newCardSize, 1, 0);

            infobox.Location = new Point(point.X, point.Y - 50);
            infobox.Size = new Size(GetCardLocation(newCardSize, 1, 12).X - (point.X - newCardSize.Width), infobox.Size.Height);
            infobox.Font = new Font(infobox.Font.FontFamily, GetFieldSize().Height / 45);

            if (!chatInvis)
            {
                ctable.Visible = true;
                cwrite.Focus();
            }

            if (this.FormBorderStyle == FormBorderStyle.FixedToolWindow)
                this.CenterToScreen();
        }

        #endregion

        /// <summary>
        /// Change the button status, depending on the players selected cards.
        /// </summary>
        /// <param name="isselected">If the cards have been selected.</param>
        private void ChangeUserButton(bool isselected)
        {
            if (cardpic.Count - (isselected ? -1 : 1) == 0)
            {
                btnOK.Text = "Skip Turn";
                btnOK.Enabled = skipEnable;

            }
            else
            {
                btnOK.Text = "OK";
                btnOK.Enabled = true;
            }
        }

        /// <summary>
        /// Get the message of the current players turn.
        /// </summary>
        private string GetPlayerTurn()
        {
            return "It is " + (playerturn == 1 ? "your" : players[playerturn - 1].Name + "'s") +
                (players[playerturn - 1].FreeTurn ? " free" : null) + " turn.";
        }

        /// <summary>
        /// Play a sound through the soundplayer (WAV files only).
        /// </summary>
        /// <param name="filename">The file to play sound.</param>
        private void PlaySound(string filename)
        {
            if (!TienLen.Properties.Settings.Default.opt_soundeffect) 
                return;

            soundplay.SoundLocation = Application.StartupPath + @"\sound\" + filename;
            soundplay.Play();
        }

        /// <summary>
        /// Change the whenever the user can make interaction or not.
        /// </summary>
        /// <param name="activate">If true, allow the user to interact with his/her cards.</param>
        private void ToggleUserGUI(bool activate)
        {
            List<PictureBox> uselist = new List<PictureBox>(13);
            uselist = GetPicList(1);

            foreach (PictureBox pic in uselist)
                pic.Enabled = activate;

            btnOK.Enabled = activate && activate == skipEnable;

            if (activate == false) 
                btnOK.Text = "Awaiting for your turn...";
        }

        #endregion

        #region Game Phase Methods

        #region Initialisation

        /// <summary>
        /// Initialise the game.
        /// </summary>
        private void HostBegin()
        {
            // Prepare the cards
            for (int i = 1; i <= 4; i++)
            {
                for (int j = 1; j <= 13; j++)
                    cardstack.Add(new CardAnim((Suite)i, (Value)j));
            }

            for (int i = 1; i <= players.Length; i++)
            {
                // This is required to preform card operations.
                cardpic = GetPicList(i);

                // Generate and sort the cards for the player
                players[i - 1].Cards = new List<Card>(GenerateCards(i));
                Parsers.SortCards(game, players[i - 1].Cards);

                players[i - 1].CardsLeft = players[i - 1].Cards.Count;
                LabelPlayerInfo(players[i - 1].Cards, i);

                // Give card tag info for each players PictureBox
                for (int y = 0; y < players[i - 1].Cards.Count; y++)
                    cardpic[y].Tag = (CardAnim)players[i - 1].Cards[y];

                CoreClient.CallOnEvent(OnEventCallType.PlayerCardsReady, players[i - 1]);

                if (players[i - 1].Type == UserType.Computer)
                    players[i - 1].AIFunc.Initialised(players[i - 1], game);

                if (SvrFunc.ServerType == SvrType.MSN)
                    SendIFCMessage(i);

                RenderCards(i);
            }

            BeginGame();

            if (SvrFunc.ServerType == SvrType.MSN)
            {
                remoteOwner.SendMessage(new TextMessage("@TLC RDY A"));
            }

            cardpic.Clear();
            cardstack.Clear();
        }

        /// <summary>
        /// Add final touches before the game can start.
        /// </summary>
        private void BeginGame()
        {
            for (int x = 1; x <= players.Length; x++)
            {
                // Set the label name for the player
                labelnames[x - 1].Text = players[x - 1].Name;
            }

            cardpic.Clear();
        }

        /// <summary>
        /// Sets the images of the players PictureBox's. GetPicList() is required to be called, for cardpic needs a collection of images to render.
        /// </summary>
        /// <param name="playerNum">The players cards to render.</param>
        private void RenderCards(int playerNum)
        {
            // Give interaction & show the cards for the main player
            //if (playerNum == 1)
                foreach (PictureBox pic in cardpic)
                {
                    // Final touches to the his/her cards
                    pic.Visible = true;
                    pic.BringToFront();

                    RevealCards(pic);

                    pic.MouseClick += new MouseEventHandler(Card_MouseClick);
                    Util.LoadCursor(pic, CursorType.Link);
                    pic.Enabled = false;

                    if (TienLen.Properties.Settings.Default.opt_roundedge) pic.BackColor = Color.Transparent;
                }
                /*
            else
                // Hide opponents cards
                foreach (PictureBox pic in cardpic)
                {
                    pic.Visible = true;
                    pic.BringToFront();

                    HideCard(pic);

                    if (TienLen.Properties.Settings.Default.opt_roundedge) pic.BackColor = Color.Transparent;
                }
                 */
        }

        #endregion

        /// <summary>
        /// End the game.
        /// </summary>
        private void EndGame()
        {
            // If playing offline, the player must have started the game to reveal the results.
            if (SvrFunc.ServerType == SvrType.Offline && btnSend.Enabled)
            {
                new MainMenu().Show();
                return;
            }

            if (new ResultsOffline(players).ShowDialog(this) == DialogResult.OK)
            {
                for (int i = 0; i < players.Length; i++)
                    players[i].Results.ClearResults();

                Client client = new Client((IGame)Activator.CreateInstance(game.GetType()), SvrType.Offline, players, remoteOwner);
                client.Show();
            }
            else if (SvrFunc.ServerType == SvrType.MSN)
            {
                foreach (Form frm in Application.OpenForms)
                    if (frm is MSNClient) frm.Show();
                
            }
            else new MainMenu().Show();

            msgclose = false;
            this.Close();
        }

        private int GetNextPlayersTurn()
        {
            for (int i = 1; i < players.Length; i++)
            {
                int tempUse = clockwiseTurn ?
                    i + playerturn > players.Length ? (i + playerturn) - players.Length : playerturn + 1 :
                    playerturn - i <= 0 ? players.Length - i + 1 : playerturn - i;

                if (players[tempUse - 1].Results.Place == 0)
                    return tempUse;
            }

            return -1;
        }

        private void ChangeTurn(bool starting)
        {
            if (SvrFunc.ServerType == SvrType.MSN && starting)
                CoreClient.CallOnEvent(OnEventCallType.GameReady);

            if (!starting)
            {
                players[playerturn - 1].FreeTurn = false;

                if (players.Length - 1 == xplace)
                {
                    players[GetNextPlayersTurn() - 1].Results.Place = players.Length;
                    EndGame();

                    return;
                }

                playerturn = GetNextPlayersTurn();

                // Clear the field's cards if it is someone's free turn.
                if (allowFreeTurn && playerfield == playerturn)
                {
                    DisposeCards();
                    cardstack.Clear();
                }

                players[playerturn - 1].FreeTurn = allowFreeTurn && playerfield == playerturn;
                CoreClient.CallOnEvent(OnEventCallType.TurnChanged, players[playerturn - 1]);
            }

            infobox.Text = GetPlayerTurn();
            infobox.Visible = true;

            // AI Player
            if (players[playerturn - 1].Type == UserType.Computer && players[0].Type == UserType.Host)
            {
                List<Card> playcards = players[playerturn - 1].AIFunc.Process(cardstack, game);

                if (playcards.Count == 0)
                    infobox.Text = players[playerturn - 1].Name + " has decided to skip the turn.";
                else
                    foreach (Card cards in playcards)
                        cardpic.Add(CardOrdToPic(cards.CardOrder, playerturn));

                EndTurn();
            }

            else if (playerturn == players[0].Number)
            {
                ToggleUserGUI(true);
                btnOK.Text = "Skip Turn";
            }

            // Though queues aren't the best idea in this situation, the messages are sent chronologically by turn, so it should be fine.
            else if (SvrFunc.ServerType == SvrType.MSN && msnpQueue.Count != 0)
            {
                ParseFieldPacket(msnpQueue.Dequeue());
                EndTurn();
            }
        }

        private void EndTurn()
        {
            bool finished = false;

            #region Player hasn't skipped

            if (cardpic.Count != 0)
            {
                // Collect the current cards in field
                cardstack.Clear();

                // Add the cards in the field to the dispose queue for later disposal
                foreach (PictureBox pic in cardpic)
                {
                    cardstack.Add((CardAnim)pic.Tag);
                    disposequeue.Add(pic);
                    players[playerturn - 1].Cards.Remove((Card)pic.Tag);

                    // If the user wants to change resolution while the card is under animation,
                    // let the procedures know that the card is beginning to animate to the field.
                    if (((CardAnim)pic.Tag).TimerAnim == null)
                        ((CardAnim)pic.Tag).TimerAnim = new AnimTimer(new Point(), 1);
                }

                players[playerturn - 1].CardsLeft -= cardpic.Count;

                //if (cardpic.Count != 0) inttemp = 0;
                playerfield = playerturn;
            }

            #endregion

            #region Player has finished

            if (players[playerturn - 1].CardsLeft == 0)
            {
                PlaySound("card_finish.wav");

                players[playerturn - 1].Results.Place = ++xplace;

                infobox.Text = players[playerturn - 1].Name + " has played all the cards!";
                playerfield = GetNextPlayersTurn();

                finished = true;
            }

            #endregion

            #region MSN Send Field Messsage

            if (SvrFunc.ServerType == SvrType.MSN &&
                (playerturn == 1 || (players[0].Type == UserType.Host && players[playerturn - 1].Type == UserType.Computer)))
            {
                if (cardpic.Count != 0)
                {
                    string struse = "@TLC FLD A " + players[playerturn - 1].ID + " ";

                    for (int i = 0; i < cardstack.Count; i++)
                        struse += cardstack[i].CardOrder + "/";

                    struse = struse.Remove(struse.Length - 1, 1);

                    remoteOwner.SendMessage(new TextMessage(struse));
                }
                else remoteOwner.SendMessage(new TextMessage("@TLC FLD B"));
            }

            #endregion

            players[playerturn - 1].Results.Turns++;
            players[playerturn - 1].SkippedTurn = cardpic.Count != 0;

            if (cardpic.Count == 0)
                StartDelayTimer(1250, GamePhase.EndTurn);
            else
                MoveCards();

            CoreClient.CallOnEvent(OnEventCallType.TurnEnded, players[playerturn - 1], finished);
        }
             
        #endregion

        #region Special Methods

        /// <summary>
        /// Verify if the cards selected by the player can be played.
        /// </summary>
        private bool ParseField()
        {
            List<Card> cardsel = new List<Card>(cardpic.Count);

            // Sort the selected cards
            cardpic.Sort(delegate(PictureBox pic1, PictureBox pic2)
            {
                Card c1 = (Card)pic1.Tag;
                Card c2 = (Card)pic2.Tag;

                if (Parsers.GetGCV(game, c1.Value).CompareTo(Parsers.GetGCV(game, c2.Value)) == 0)
                    return c1.Suite.CompareTo(c2.Suite);
                else return Parsers.GetGCV(game, c1.Value).CompareTo(Parsers.GetGCV(game, c2.Value));
            });

            // Add each selected card into the list
            for (int i = 0; i < cardpic.Count; i++)
                cardsel.Add((Card)cardpic[i].Tag);

            return game.ValidateCards(cardsel, cardstack);
        }

        #endregion

        #region Animation

        #region Timers

        /// <summary>
        /// Change the timer phase.
        /// </summary>
        /// <param name="interval">Period of time you want to stay at this phase.</param>
        /// <param name="phase">The new game phase.</param>
        private void StartDelayTimer(int interval, GamePhase phase)
        {
            delay.Tag = phase;
            delay.Interval = interval;
        }

        private void delay_Tick(object sender, EventArgs e)
        {
            switch ((GamePhase)delay.Tag)
            {
                case GamePhase.Animating:
                    {
                        if (cardstack.Count != cardpic.Count)
                        {
                            int indexUse = cardstack.Count - cardpic.Count - 1;

                            RevealCards(CardOrdToPic(cardstack[indexUse].CardOrder, playerturn));
                            PlaySound("card_flick.wav");
                        }

                        if (cardpic.Count == 0)
                            StartDelayTimer(800, GamePhase.EndTurn);
                        else
                        {
                            MoveCardsGUI(cardpic[0]);
                            cardpic.Remove(cardpic[0]);
                        }

                        break;
                    }
                case GamePhase.Message:
                    {
                        StartDelayTimer(1000, GamePhase.None);
                        infobox.Text = GetPlayerTurn();

                        exnote.Text = string.Empty;
                        exnote.Visible = false;

                        break;
                    }
                case GamePhase.EndTurn:
                    {
                        StartDelayTimer(1000, GamePhase.None);
                        CoreClient.CallOnEvent(OnEventCallType.GamePhaseChanged, players[playerturn - 1], GamePhase.EndTurn);

                        ChangeTurn(false);

                        break;
                    }
            }
        }

        #endregion

        private void MoveCardsGUI(PictureBox pic)
        {
            CardAnim tag = (CardAnim)pic.Tag;

            if (TienLen.Properties.Settings.Default.opt_anim)
                pic.BringToFront();

            tag.TimerAnim = new AnimTimer(new Point(slots[tag.CentreSlot - 1].Location.X - pic.Location.X, slots[tag.CentreSlot - 1].Location.Y - pic.Location.Y), 1);
            tag.TimerAnim.Interval = 5;

            tag.TimerAnim.Tick += delegate(object sender, EventArgs e)
            {
                if (TienLen.Properties.Settings.Default.opt_anim)
                    pic.Location = new Point(pic.Location.X + tag.TimerAnim.SizeIncrement.X, pic.Location.Y + tag.TimerAnim.SizeIncrement.Y);

                if (tag.TimerAnim.TicksNeeded == tag.TimerAnim.Elasped)
                {
                    // If it has reached to the area, where it can be placed on the field
                    tag.TimerAnim.Stop();
                    tag.TimerAnim = null;

                    pic.BringToFront();
                    pic.Location = slots[tag.CentreSlot - 1].Location;
                }
            };

            tag.TimerAnim.Enabled = true;
        }

        private void MoveCards()
        {
            // Depending on the number of cards selected, assign them to the field slot position of
            // where they should go.
            switch (cardpic.Count)
            {
                case 1:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 7;
                         break;

                case 2:

                         ((CardAnim)cardpic[0].Tag).CentreSlot = 6;
                         ((CardAnim)cardpic[1].Tag).CentreSlot = 8;
                        break;

                case 3:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 5;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 7;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 9;
                        break;
                case 4:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 4;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 6;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 8;
                        ((CardAnim)cardpic[3].Tag).CentreSlot = 10;

                        break;

                case 5:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 2;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 4;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 6;
                        ((CardAnim)cardpic[3].Tag).CentreSlot = 8;
                        ((CardAnim)cardpic[4].Tag).CentreSlot = 10;

                        break;

                case 6:
                case 7:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 1;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 3;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 5;
                        ((CardAnim)cardpic[3].Tag).CentreSlot = 7;
                        ((CardAnim)cardpic[4].Tag).CentreSlot = 9;
                        ((CardAnim)cardpic[5].Tag).CentreSlot = 11;

                        if (cardpic.Count == 7) ((CardAnim)cardpic[6].Tag).CentreSlot = 13;

                        break;

                case 8:
                case 9:

                        int i = 0;
                        int j = cardpic.Count == 9 ? 3 : 4;

                        if (cardpic.Count == 9)
                            ((CardAnim)(CardAnim)cardpic[i++].Tag).CentreSlot = j;

                        for ( ; i < cardpic.Count; i++)
                            ((CardAnim)(CardAnim)cardpic[i].Tag).CentreSlot = i + j;

                        break;

                case 10:
                case 11:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 2;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 3;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 4;
                        ((CardAnim)cardpic[3].Tag).CentreSlot = 5;
                        ((CardAnim)cardpic[4].Tag).CentreSlot = 6;
                        ((CardAnim)cardpic[5].Tag).CentreSlot = 7;
                        ((CardAnim)cardpic[6].Tag).CentreSlot = 8;
                        ((CardAnim)cardpic[7].Tag).CentreSlot = 9;
                        ((CardAnim)cardpic[8].Tag).CentreSlot = 10;
                        ((CardAnim)cardpic[9].Tag).CentreSlot = 11;

                        if (cardpic.Count == 11) ((CardAnim)cardpic[10].Tag).CentreSlot = 12;
                        break;

                case 12:
                case 13:

                        ((CardAnim)cardpic[0].Tag).CentreSlot = 1;
                        ((CardAnim)cardpic[1].Tag).CentreSlot = 2;
                        ((CardAnim)cardpic[2].Tag).CentreSlot = 3;
                        ((CardAnim)cardpic[3].Tag).CentreSlot = 4;
                        ((CardAnim)cardpic[4].Tag).CentreSlot = 5;
                        ((CardAnim)cardpic[5].Tag).CentreSlot = 6;
                        ((CardAnim)cardpic[6].Tag).CentreSlot = 7;
                        ((CardAnim)cardpic[7].Tag).CentreSlot = 8;
                        ((CardAnim)cardpic[8].Tag).CentreSlot = 9;
                        ((CardAnim)cardpic[9].Tag).CentreSlot = 10;
                        ((CardAnim)cardpic[10].Tag).CentreSlot = 11;
                        ((CardAnim)cardpic[11].Tag).CentreSlot = 12;

                        if (cardpic.Count == 13) ((CardAnim)cardpic[12].Tag).CentreSlot = 13;
                        break;
            }

            StartDelayTimer(500, GamePhase.Animating);
        }
        

        private void Card_MouseClick(object sender, MouseEventArgs e)
        {
            PictureBox pic = (PictureBox)sender;
            CardAnim tag = (CardAnim)pic.Tag;

            if (e.Button != MouseButtons.Left || !pic.Enabled || pic.IsDisposed)
                return;

            tag.IsSelected = !tag.IsSelected;
            ChangeUserButton(tag.IsSelected);

            if (tag.IsSelected) cardpic.Add(pic);
            else cardpic.Remove(pic);

            if (TienLen.Properties.Settings.Default.opt_anim)
            {
                pic.Enabled = false;

                tag.TimerAnim = new AnimTimer(new Point(0, tag.IsSelected ? -20 : 20), 0);
                tag.TimerAnim.Interval = 10;

                tag.TimerAnim.Tick += delegate(object sender2, EventArgs e2)
                {
                    if (tag.TimerAnim.Elasped == tag.TimerAnim.TicksNeeded)
                    {
                        pic.Enabled = true;

                        tag.TimerAnim.Stop();
                        tag.TimerAnim = null;
                    }
                    else
                        pic.Location = new Point(pic.Location.X, pic.Location.Y + tag.TimerAnim.SizeIncrement.Y);
                };

                tag.TimerAnim.Enabled = true;

            }
            else
                pic.Location = new Point(pic.Location.X, pic.Location.Y + (tag.IsSelected ? -20 : 20));
        }

        #endregion

        #region Key Press Handling

        private bool IsUnrevealed(PictureBox picture)
        {
            return (!picture.IsDisposed && (disposequeue.Count == 0 || disposequeue.Find(delegate(PictureBox pic2) { return picture == pic2; }) == null));
        }

        private PictureBox GetCardPlayer(int cardNum)
        {
            List<PictureBox> picList = new List<PictureBox>(13);
            picList = GetPicList(1);

            for (int i = 12; i >= 0; i--)
                if (!IsUnrevealed(picList[i]))
                {
                    picList.Remove(picList[i]);
                }

            return picList[cardNum - 1];
        }

        private void cwrite_KeyPress(object sender, KeyPressEventArgs e)
        {
            // ENTER key
            if (e.KeyChar == '\r')
            {
                // Shift + Enter = new line
                if (ModifierKeys == Keys.Shift) return;

                if (string.IsNullOrEmpty(cwrite.Text))
                {
                    e.Handled = true;
                    return;
                }
                else
                    // Valid message (not empty text)
                    csend_Click(null, EventArgs.Empty);
            }

            // Ctrl + C button
            else if (ModifierKeys == Keys.Control && (e.KeyChar == 'c' || e.KeyChar == 'C'))
            {
                ctable.Visible = false;
                cwrite.Text = string.Empty;

                e.Handled = true;
                this.Focus();
            }

            // ESC Key = Full screen
            else if (e.KeyChar == (char)Keys.Escape && ModifierKeys == Keys.None)
            {
                btnFullScreen_Click(null, EventArgs.Empty);
                return;
            }
        }

        private void Client_KeyPress(object sender, KeyPressEventArgs e)
        {
            // Full Screen
            if (e.KeyChar == (char)Keys.Escape && ModifierKeys == Keys.None)
            {
                btnFullScreen_Click(null, EventArgs.Empty);
                return;
            }

            // Begin game if it has not begun, otherwise skip turn / send selected cards to field
            else if (e.KeyChar == (char)Keys.Enter)
            {
                if (btnSend.Enabled) btnSend_Click(null, EventArgs.Empty);
                else if (btnOK.Enabled) btnOK_Click(sender, EventArgs.Empty);

                return;
            }

            // Ctrl + C = MSN Chat enabled
            else if (SvrFunc.ServerType == SvrType.MSN && ModifierKeys == Keys.Shift && (e.KeyChar == 'C' || e.KeyChar == 'c'))
            {
                ctable.Visible = true;
                cwrite.Focus();

                return;
            }

            // From the tilde to the plus key, these keys can be used to select the players cards
            if (playerturn == 1)
            {
                MouseEventArgs mouseEvent = new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0);
                int keyValue = (int)e.KeyChar;

                // Tilde key (~)
                if (keyValue == 96)
                    Card_MouseClick(GetCardPlayer(1), mouseEvent);

                // Vertical key row, from 1 to 9 in your keyboard.
                else if (keyValue > 48 && keyValue <= 57 && keyValue - 47 <= players[0].CardsLeft)
                    Card_MouseClick(GetCardPlayer(keyValue - 47), mouseEvent);

                // Number 0 on the vertical key row.
                else if (keyValue == 48 && players[0].CardsLeft >= 11)
                    Card_MouseClick(GetCardPlayer(11), mouseEvent);

                // Keys.OemMinus key (- _ )
                else if (keyValue == 45 && players[0].CardsLeft >= 12)
                    Card_MouseClick(GetCardPlayer(12), mouseEvent);

                // Keys.OemPlus key ( + = )
                else if (keyValue == 61 && players[0].CardsLeft == 13)
                    Card_MouseClick(CardOrdToPic(13, 1), mouseEvent);
            }
        }

        #endregion


        /// <summary>
        /// Dispose the cards in the disposequeue and clear the list.
        /// </summary>
        private void DisposeCards()
        {
            // Dispose un-needed cards in field
            foreach (PictureBox pic in disposequeue)
                pic.Dispose();

            disposequeue.Clear();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            // Now find the user who has the 3 of Spades, and commence the game with their first move
            ChangeTurn(true);
            btnSend.Enabled = false;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (btnOK.Text != "Skip Turn" && !ParseField())
            {
                // Invalid move
                infobox.Text = "Invalid Move";
                StartDelayTimer(1500, GamePhase.Message);

                return;
            }

            ToggleUserGUI(false);
            EndTurn();
        }

        private void Client_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Show a warning MessageBox if necessary
            if (msgclose && MessageBox.Show("Are you sure you want to close the game?", "Game Closing",
                MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
            {
                if (SvrFunc.ServerType == SvrType.MSN)
                {
                    SvrFunc.MSN.Nameserver.LeaveMultiparty(remoteOwner);

                    foreach (Form frm in Application.OpenForms)
                        if (frm is MSNClient) frm.Show();
                }
                else
                    EndGame();
            }
            // If the player doesn't want to exit the client
            else if (msgclose)
            {
                e.Cancel = true;
                return;
            }

            if (SvrFunc.ServerType == SvrType.MSN)
                SvrFunc.MSN.MessageManager.TextMessageReceived -= MessageManager_TextMessageReceived;

            GameEvents.ClientFuncCalled -= GameEvents_ClientFuncCalled;
            GameEvents.ClientPropChangeCalled -= GameEvents_ClientPropChangeCalled;
            game.Dispose();

            GC.Collect();
        }

        private void btnFullScreen_Click(object sender, EventArgs e)
        {
            if (btnFullScreen.Text == "Go Full Screen!")
            {
                ChangeSize(Screen.PrimaryScreen.Bounds.Size);
                btnFullScreen.Text = "Return to Windowed Mode";
            }
            else
            {
                ChangeSize(prevSize);
                btnFullScreen.Text = "Go Full Screen!";
            }
        }

        private void Client_ResizeEnd(object sender, EventArgs e)
        {
            if (prevSize != this.Size)
            {
                ChangeSize(this.Size);
                prevSize = this.Size;
            }
        }
    }
}